package com.artnology.independence.model;

import java.awt.Color;
import java.awt.Shape;
import java.text.DecimalFormat;
import java.util.HashSet;
import java.util.Set;

import com.artnology.independence.Activator;
import com.artnology.independence.model.relationships.DVReference;
import com.artnology.independence.views.DependencyView;

public abstract class DVNode extends DVModelObject {

	public void setReferencesTo(Set<DVReference> referencesTo) {
		this.referencesTo = referencesTo;
	}

	private Set<DVReference> referencesTo = new HashSet<DVReference>();
	private Set<DVNode> referencedFromNodes = null;
	private Set<DVNode> referencedNodes = null;

	private DecimalFormat instabilityFormat = new DecimalFormat("#.##");
	private DecimalFormat couplingPercentageFormat = new DecimalFormat("##");

	public abstract Color getNodeFillColor();

	public Set<DVReference> getReferencesTo() {
		return referencesTo;
	}

	public boolean isPhantom() {
		return getGraphModel().getAllPhantomTypes().contains(this);
	}
	public abstract Shape getPaintShape(double width, double height);

	public boolean addReferenceTo(DVReference additionalReference) {
		for (DVReference reference : referencesTo) {
			if (reference.equals(additionalReference)) {
				reference.increaseWeight();
				return false;
			}
		}
		Set<DVReference> incomingRefs = new HashSet<DVReference>();
		incomingRefs.add(additionalReference);
		Set<DVReference> putRefs = getGraphModel().getIncomingReferencesMap()
				.putIfAbsent(additionalReference.getReferencedNode(),
						incomingRefs);
		if (putRefs != null) {
			putRefs.add(additionalReference);
		} else {
			incomingRefs.add(additionalReference);
		}
		return referencesTo.add(additionalReference);

	}

	public DVNode(String simpleName, DVGraphModel model) {
		super(simpleName, model);

	}

	public String getLabel() {
		// default impl, to be overriden in subclass
		return getName();
	}

	public Set<DVReference> getAllIncidentReferences() {
		Set<DVReference> returnSet = new HashSet<DVReference>();
		returnSet.addAll(getReferencesTo());
		Set<DVReference> incomingRefs = getGraphModel()
				.getIncomingReferencesMap().get(this);
		if (incomingRefs != null) {
			returnSet.addAll(incomingRefs);
		}
		return returnSet;
	}

	public Set<DVNode> getReferencedFromNodes() {
		if (referencedFromNodes == null) {
			referencedFromNodes = new HashSet<DVNode>();
			for (DVNode fromNode : getGraphModel().getAllTypes()) {
				for (DVModelObject referencedNode : fromNode
						.getReferencedNodes()) {
					if (referencedNode.equals(this)) {
						referencedFromNodes.add(fromNode);
					}
				}
			}
		}
		return referencedFromNodes;
	}

	public Set<DVNode> getReferencedNodes() {
		if (referencedNodes == null) {
			referencedNodes = new HashSet<DVNode>();
			for (DVReference ref : this.getReferencesTo()) {
				referencedNodes.add(ref.getReferencedNode());
			}
		}
		return referencedNodes;
	}

	public Set<DVNode> getVisibleReferencedFromNodes() {
		Set<DVNode> visibleRefFromNodes = new HashSet<DVNode>();
		for (DVNode dvNode : getReferencedFromNodes()) {
			if (getGraphModel().getMyGraph().getGraph().containsVertex(dvNode)) {
				visibleRefFromNodes.add(dvNode);
			}
		}
		return visibleRefFromNodes;
	}

	public Set<DVNode> getVisibleReferencedNodes() {
		Set<DVNode> visibleRefNodes = new HashSet<DVNode>();
		for (DVNode dvNode : getReferencedNodes()) {
			if (getGraphModel().getMyGraph().getGraph().containsVertex(dvNode)) {
				visibleRefNodes.add(dvNode);
			}
		}
		return visibleRefNodes;
	}

	public int getAfferentCoupling() {
		return getVisibleReferencedFromNodes().size();
	}

	public int getEfferentCoupling() {
		return getVisibleReferencesTo().size();
	}

	public int getTotalCoupling() {
		return getAfferentCoupling() + getEfferentCoupling();
	}

	public float getInstability() {
		if (getTotalCoupling() > 0) {
			return (float) getEfferentCoupling() / (float) getTotalCoupling();
		}
		return 0;
	}

	public float getCouplingPercentage() {
		if (getTotalCoupling() == 0) {
			return 0f;
		}
		// } else {
		// return (float) DVProject.getInstance().getCouplingRank(this)
		// / (float) DVProject.getInstance()
		// .getNumberOfCurrentlyVisibleNodes();
		// }
		return 0f;
	}

	public Color getCouplingColorCode() {
		float couplingPercentage = getCouplingPercentage();
		if (couplingPercentage > 0.8f) {
			return Color.RED;
		} else if (couplingPercentage <= 0.8f && couplingPercentage > 0.6f) {
			return new Color(255, 150, 0);
		} else if (couplingPercentage <= 0.6f && couplingPercentage > 0.4f) {
			return Color.YELLOW;
		} else if (couplingPercentage <= 0.4f && couplingPercentage > 0.2f) {
			return new Color(150, 255, 0);
		} else {
			return Color.GREEN;
		}
	}

	public boolean hasCrossPackageReference() {
		for (DVReference reference : getAllIncidentReferences()) {
			if (!((DVType) reference.getReferenceFromNode()).getPackage()
					.equals(((DVType) reference.getReferencedNode())
							.getPackage())) {
				return true;
			}
		}
		return false;
	}

	public boolean hasVisibleCrossPackageReference() {
		for (DVReference reference : getGraphModel().getMyGraph().getGraph()
				.getIncidentEdges(this)) {
			if (!((DVType) reference.getReferenceFromNode()).getPackage()
					.equals(((DVType) reference.getReferencedNode())
							.getPackage())) {
				return true;
			}
		}
		return false;
	}

	@Override
	public String toString() {
		String retString = "";
		retString += getLabel();
		if (Activator.getDefault().getPrefStore()
				.getBoolean(DependencyView.SHOW_COUPLING)) {

			retString += "   "
					+ couplingPercentageFormat
							.format(getCouplingPercentage() * 100) + "%::Ca="
					+ getAfferentCoupling() + "::Ce=" + getEfferentCoupling()
					+ "::I=" + instabilityFormat.format(getInstability());
		}
		return retString;
	}

	public Set<DVReference> getVisibleReferencesTo() {
		Set<DVReference> referencesTo = new HashSet<DVReference>();
		// based on current visible nodes and edges in graph
		for (DVReference reference : getReferencesTo()) {
			if (getGraphModel().getMyGraph().getGraph().containsEdge(reference)) {
				referencesTo.add(reference);
			}
		}
		return referencesTo;
	}

}